package com.itheima.demo4;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;

public class MyArrayList<E> {
    private Object[] elementData = {};
    private int size; // 记录元素个数，也记录下次添加元素的位置，比如说有数组[0,1]两个元素，数组元素size为2，数组下个添加元素的位置也为2
    private int DEFAULT_CAPACITY = 10;      //默认容量

    public boolean add(E e) {
        // 1、判断是否需要扩容。一开始第一次调用add方法时size和elementData的大小都是0
        //第二次扩容就是size和elementData的大小都到了10
        if(size == elementData.length) {
            grow();
        }
        //size记录元素个数，也记录的是下次添加元素的位置。添加完之后，size需要再+1指向下下次添加元素的位置
        //这里是先调size原值参与程序，然后再++。左前右后
        elementData[size++] = e;
        //数组添加元素允许重复，所以肯定能成功，返回true
        return true;
    }


    public E get(int index) {
        // 做越界判断。防止索引大小超过数组长度，引发索引越界异常
        checkIndex(index);
        // 根据索引提取数据
         /* java.util.ArrayList<E>
 *     这里的<E>就是泛型，表示元素的数据类型
 *  ArrayList集合的设计者在定义ArrayList集合时，就已经明确ArrayList集合时给别人装数据用的，
 * 但是别人用ArrayList集合时候，装什么类型的数据他不知道，所以就用一个`<E>`表示元素的数据类型。
 *当别人使用ArrayList集合创建对象时，`new ArrayList<String> `就表示元素为String类型，`new ArrayList<Integer>`表示元素为Integer类型。
 *
 * 泛型的好处：在编译阶段可以避免出现一些非法的数据
 * 作用：泛型提供了在编译阶段约束所能操作的数据类型，并自动进行检查的能力！这样可以避免强制类型转换，及其可能出现的异常。
 *  泛型的本质：把具体的数据类型作为参数传给类型变量。
        */
        //elementData是object数组，但是存入的元素已经被泛型E类约束好了，所以添加的数据类型一定是E类型的数据，所以把返回的数据强转就行了
        return (E) elementData[index];
    }

    public E remove(int index) {
        checkIndex(index);
        E e = (E) elementData[index];       //返回要被删除的数据
        // 删除数据应该要考虑是否需要进行元素移位操作。删除元素后，要考虑其他元素是否要移位
        //[0,0,0,0,0, ]    举例：此时size为5，也指明了下一个元素添加的索引位置为5。
        // 0,1,2,3,4,5      假设删除索引4位置的元素，5-4=1那么剩余的数组元素不需要移位。
        //  假设删除索引2位置的元素，5-2=3,那么从索引3位置到后面的元素需要移位，共5-2-1=2个元素需要移位（因为数组是从索引0开始的，所以移位元素的个数需要额外减一）
        //弹幕：这里实际上应该理解为siez-1-index，因为size-1代表最后一位，再减去index就可知道是否移位了
        int moveFlag = size - index - 1;        //index索引处元素删除后，需要进行移位元素的个数
        if(moveFlag != 0) {
            // 把数据进行移位操作。
            //可以用官方的arraycopy方法，也可以自己写个算法，
           /* arraycopy(Object src,  int  srcPos,
                        Object dest, int destPos,
                        int length)*/
            System.arraycopy(elementData,
                    index + 1, elementData, index,
                    moveFlag);
            //原数组和目标数组都是elementData
            //int  srcPos代表从哪里开始拷贝，要从被删除数据的下一个数据开始拷贝：所以位置是index+1
            //int destPos：拷贝到目标数组的哪个位置，就是倍删除元素的索引位置，拷贝moveFlag元素
        }
        // 情况一：moveFlag==0了，当前删除元素之后的数组已经进行移位，把改变后的数组的最后一个数据改成null
        //情况二：
        elementData[--size] = null;
        return e;
    }

    public int size(){
        return size;
    }

    public void forEach(MyConsumer<E> action) {
        //校验是否为空
        Objects.requireNonNull(action);
        //回调函数式接口的accept方法
        for (int i = 0; i < size; i++) {
            action.accept((E) elementData[i]);
        }
    }

    public void checkIndex(int index) {
        //这里很巧妙，如果是我，我会写index>=elementData.length了，但是用size就好了，因为size代表了下一个元素要添加的数组索引位置
        //index也不能等于size，假设现在的数组元素size是8个，那索引到了8，说明要取第9个元素
        if(index < 0 || index >= size) {
            //不返回索引了，和官方一样，返回一个异常
            throw new IndexOutOfBoundsException(index + " out of max length " + size);
        }
    }

    /**
     * 扩容
     */
    private void grow() {
        //size为0，说明是第一次扩容
        if(size == 0) {
            elementData = new Object[DEFAULT_CAPACITY];
        }else {
            //size不为0，并且前面add方法也说明size == elementData.length，也就是都与默认容量10，已经满了
            // 后几次扩容:变成原来的1.5倍，不能直接elementData*1.5，因为有小数，需要转化。
            //所以用elementData.length >> 1右移一位的方法得到原来的一半，再加上原来的长度。这样就扩容1.5倍了
            elementData = Arrays.copyOf(elementData, elementData.length + (elementData.length >> 1));
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int i = 0; i < size; i++) {
            E e = (E) elementData[i];
            sb.append(e).append(i == size - 1 ? "" : ", ");
        }
        sb.append("]");
        return sb.toString();
    }
}
